/*
 *  Copyright (C) 2025 ameise <ameise.wang@gmail.com> - All Rights Reserved
 *
 *  This file is part of e3utils.
 *
 *  e3utils is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  e3utils is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with e3utils. If not, see <https://www.gnu.org/licenses/>.
 */

package bytes_buffer

import (
	"gitee.com/ameise84/e3utils/math"
	"gitee.com/ameise84/e3utils/str_conv"
	"io"
)

// buffer 实现了类似于ringBuffer的效果,在使用上,比ringBuffer方便(不存在返回2段的情况),在效率上略微低于ringBuffer(存在拷贝偏移数据)

type ShiftBuffer struct {
	bytes       []byte //字节
	cap         int    //容量
	maxCap      int
	vrIndex     int  //虚读位置
	rIndex      int  //已读位置
	wIndex      int  //已写位置
	isInVirtual bool //是否在虚读状态
	autoGrow    bool //是否自动增长
}

func NewShiftBuffer(cap int, maxCap int, autoGrow bool) ShiftBuffer {
	if maxCap != 0 && maxCap < cap {
		maxCap = cap
	}
	if !autoGrow && maxCap == 0 {
		maxCap = cap
	}
	return ShiftBuffer{
		bytes:    make([]byte, cap),
		cap:      cap,
		maxCap:   maxCap,
		autoGrow: autoGrow,
	}
}

func Warp(b []byte) Reader {
	return &ShiftBuffer{
		bytes:    b,
		cap:      cap(b),
		wIndex:   len(b),
		autoGrow: false,
	}
}

func (ts *ShiftBuffer) IsCanGrow() bool {
	return ts.autoGrow
}

func (ts *ShiftBuffer) GetMaxCapacity() int {
	return ts.maxCap
}

func (ts *ShiftBuffer) GetCapacity() int {
	return ts.cap
}

func (ts *ShiftBuffer) GetDataSize() int {
	return ts.wIndex - ts.rIndex
}

func (ts *ShiftBuffer) GetEmptySize() int {
	return ts.cap - ts.GetDataSize()
}

func (ts *ShiftBuffer) IsEmpty() bool {
	return ts.wIndex == ts.rIndex
}

func (ts *ShiftBuffer) Clean() {
	ts.rIndex = 0
	ts.vrIndex = 0
	ts.wIndex = 0
	ts.isInVirtual = false
}

func (ts *ShiftBuffer) GrowTo(cap int) error {
	if cap <= 0 {
		return ErrReserveZero
	}
	if cap == ts.cap {
		return nil
	}
	if ts.maxCap != 0 && cap > ts.maxCap {
		return ErrReserveBigger
	}
	if cap < ts.GetDataSize() {
		return ErrReserveSmaller
	}

	newBytes := make([]byte, cap)
	if ts.wIndex > ts.rIndex {
		copy(newBytes, ts.bytes[ts.rIndex:ts.wIndex])
	}
	ts.bytes = newBytes
	ts.cap = cap
	ts.vrIndex -= ts.rIndex
	ts.wIndex -= ts.rIndex
	ts.rIndex = 0
	return nil
}

func (ts *ShiftBuffer) GrowLen(add int) (int, error) {
	newCap := ts.cap + add
	err := ts.GrowTo(newCap)
	return ts.cap, err
}

func (ts *ShiftBuffer) AssignString(s string) error {
	ts.Clean()
	return ts.AppendBytes(str_conv.ToBytes(s))
}

func (ts *ShiftBuffer) AssignBytes(p []byte) error {
	ts.Clean()
	return ts.AppendBytes(p)
}

func (ts *ShiftBuffer) AssignByte(p byte) error {
	ts.Clean()
	return ts.AppendByte(p)
}

func (ts *ShiftBuffer) AppendString(s string) error {
	return ts.AppendBytes(str_conv.ToBytes(s))
}

func (ts *ShiftBuffer) AppendBytes(p []byte) error {
	dataSize := len(p)
	if dataSize == 0 {
		return nil
	}
	usedSize := ts.GetDataSize()
	toSize := usedSize + dataSize
	if ts.maxCap != 0 && toSize > ts.maxCap {
		return io.ErrShortWrite
	}
	if toSize > ts.cap {
		if !ts.autoGrow {
			return io.ErrShortWrite
		}

		toCap := math.CeilToPowerOfTwo(dataSize + ts.cap)
		if ts.maxCap != 0 && toCap > ts.maxCap {
			toCap = ts.maxCap
		}
		_ = ts.GrowTo(toCap)
	}

	if ts.wIndex+dataSize > ts.cap {
		ts.moveHeadZeroRIndex()
	}
	n := copy(ts.bytes[ts.wIndex:], p)
	ts.wIndex += n
	return nil
}

func (ts *ShiftBuffer) AppendByte(p byte) error {
	return ts.AppendBytes([]byte{p})
}

func (ts *ShiftBuffer) WriteString(s string) (int, error) {
	return ts.Write(str_conv.ToBytes(s))
}

func (ts *ShiftBuffer) Write(p []byte) (int, error) {
	dataSize := len(p)
	tailSize := ts.cap - ts.wIndex
	if tailSize < dataSize {
		ts.moveHeadZeroRIndex()
	}
	emptySize := ts.GetEmptySize()
	if emptySize < dataSize && ts.IsCanGrow() {
		toCap := math.CeilToPowerOfTwo(ts.cap + dataSize - emptySize)
		if ts.maxCap != 0 && toCap > ts.maxCap {
			toCap = ts.maxCap
		}
		_ = ts.GrowTo(toCap)
	}
	n := copy(ts.bytes[ts.wIndex:], p)
	ts.wIndex += n
	if n < dataSize {
		return n, io.ErrShortWrite
	}
	return n, nil
}

func (ts *ShiftBuffer) Peek() ([]byte, error) {
	if ts.wIndex == ts.rIndex {
		return nil, io.EOF
	}
	return ts.bytes[ts.rIndex:ts.wIndex], nil
}

func (ts *ShiftBuffer) PeekLen(outLen int) ([]byte, error) {
	if ts.wIndex == ts.rIndex {
		return nil, io.EOF
	}
	toIdx := ts.rIndex + outLen
	if ts.wIndex < toIdx {
		return nil, io.ErrUnexpectedEOF
	}
	return ts.bytes[ts.rIndex:toIdx], nil
}

func (ts *ShiftBuffer) VirtualFetchLen(outLen int) (out []byte, err error) {
	if outLen == 0 {
		return nil, ErrVirtualReadLen
	}
	if ts.vrIndex == ts.wIndex {
		return nil, io.EOF
	}
	vrTo := ts.vrIndex + outLen
	if vrTo > ts.wIndex {
		return nil, io.ErrUnexpectedEOF
	}
	out = ts.bytes[ts.vrIndex:vrTo]
	ts.vrIndex = vrTo
	ts.isInVirtual = true
	return
}

func (ts *ShiftBuffer) VirtualCommit() {
	if ts.isInVirtual {
		if ts.vrIndex == ts.wIndex {
			ts.Clean()
		} else {
			ts.rIndex = ts.vrIndex
			ts.tryMoveHeadZeroRIndex()
			ts.isInVirtual = false
		}
	}
}

func (ts *ShiftBuffer) VirtualRecover() {
	ts.vrIndex = ts.rIndex
	ts.isInVirtual = false
}

func (ts *ShiftBuffer) Fetch() (out []byte, err error) {
	if ts.isInVirtual {
		return nil, ErrInVirtualRead
	}
	if ts.wIndex == ts.rIndex {
		return nil, io.EOF
	}
	out = ts.bytes[ts.rIndex:ts.wIndex]
	ts.Clean()
	return
}

func (ts *ShiftBuffer) FetchLen(outLen int) (out []byte, err error) {
	if ts.isInVirtual {
		return nil, ErrInVirtualRead
	}
	if ts.rIndex == ts.wIndex {
		return nil, io.EOF
	}
	rTo := ts.rIndex + outLen
	if rTo > ts.wIndex {
		return nil, io.ErrUnexpectedEOF
	}
	out = ts.bytes[ts.rIndex:rTo]
	if rTo == ts.wIndex {
		ts.Clean()
	} else {
		ts.rIndex = rTo
		ts.vrIndex = rTo
		ts.tryMoveHeadZeroRIndex()
	}
	return
}

func (ts *ShiftBuffer) ReadAll(out []byte) (int, error) {
	if len(out) < ts.GetDataSize() {
		return 0, io.ErrShortBuffer
	}
	src, err := ts.Fetch()
	if err != nil {
		return 0, err
	}
	_ = copy(out, src)
	return len(src), nil
}

func (ts *ShiftBuffer) ReadLen(out []byte, outLen int) error {
	src, err := ts.FetchLen(outLen)
	if err != nil {
		return err
	}
	copy(out, src)
	return nil
}

func (ts *ShiftBuffer) Read(out []byte) (int, error) {
	if ts.isInVirtual {
		return 0, ErrInVirtualRead
	}
	if ts.rIndex == ts.wIndex {
		return 0, io.EOF
	}
	size := copy(out, ts.bytes[ts.rIndex:ts.wIndex])
	ts.rIndex += size
	return size, nil
}

func (ts *ShiftBuffer) GetTailEmptyBytes() []byte {
	size := ts.cap - ts.wIndex
	if size == 0 {
		ts.moveHeadZeroRIndex()
	}
	return ts.bytes[ts.wIndex:]
}

func (ts *ShiftBuffer) AddLen(len int) {
	ts.wIndex += len
}

func (ts *ShiftBuffer) ResetLen(len int) {
	ts.wIndex = ts.rIndex + len
}

func (ts *ShiftBuffer) tryMoveHeadZeroRIndex() {
	dataSize := ts.GetDataSize()
	if dataSize < 256 && ts.rIndex*2 > ts.cap && (ts.wIndex-ts.rIndex)*16 <= ts.cap {
		ts.moveHeadZeroRIndex()
	}
}

func (ts *ShiftBuffer) moveHeadZeroRIndex() {
	if ts.wIndex != ts.rIndex && ts.rIndex != 0 {
		copy(ts.bytes, ts.bytes[ts.rIndex:ts.wIndex])
		ts.wIndex -= ts.rIndex
		ts.vrIndex -= ts.rIndex
		ts.rIndex = 0
	}
}
